package com.heima.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.heima.dto.LoginDto;
import com.heima.mapper.RoleMapper;
import com.heima.mapper.UsersMapper;
import com.heima.mapper.UsersRoleMapper;
import com.heima.pojo.Role;
import com.heima.pojo.Users;
import com.heima.pojo.UsersRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class LoginService implements UserDetailsService {

    @Autowired
    private JwtEncoder jwtEncoder;

    @Autowired
    private UsersMapper usersMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UsersRoleMapper usersRoleMapper;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 目标：前端传给我一个用户名，我要通过这个用户名查出 用户账号、密码、id、角色列表
        // 用户表 -> 用户角色关系表 -> 角色表 -> 角色权限关系表 -> 权限表

        // 1. 查询当前用户信息
        LambdaQueryWrapper<Users> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Users::getUsername, username);
        Users users = usersMapper.selectOne(wrapper);
        if (users == null) {
            throw new UsernameNotFoundException(String.format("'%s' not existed!", username));
        }

        // 2. 拿用户的id去获取它拥有哪些角色列表
        // 2.1 拿着用户id，到用户角色关系表中，查出对应的 角色id列表
        LambdaQueryWrapper<UsersRole> wrapper1 = new LambdaQueryWrapper<>();
        wrapper1.eq(UsersRole::getUserId, users.getId());
        List<UsersRole> usersRoles = usersRoleMapper.selectList(wrapper1);
        List<String> roleIdList = usersRoles.stream().map(UsersRole::getRoleId).collect(Collectors.toList());

        // 3. 拿着 角色id列表，到角色表中，去获取角色的名称
        LambdaQueryWrapper<Role> wrapper2 = new LambdaQueryWrapper<>();
        wrapper2.in(Role::getId, roleIdList);
        List<Role> roles = roleMapper.selectList(wrapper2);
        // ["app", "aaa"]
        List<String> roleNameList = roles.stream().map(Role::getName).collect(Collectors.toList());

        Collection<GrantedAuthority> list =roleNameList.stream().map(item -> {
            // item 是什么？
            // 1. new一个GrantedAuthority
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(item);
            // 2. 给GrantedAuthority赋值
            // 3. return
            return grantedAuthority;
        }).collect(Collectors.toList());

        // 4. 把所用的信息组装到一个UserDetails对象里面
        Users userDetails = new Users();
        userDetails.setId(users.getId());
        userDetails.setUsername(users.getUsername());
        userDetails.setPassword(users.getPassword());
        userDetails.setAccountNonExpired(true);
        userDetails.setAccountNonLocked(true);
        userDetails.setCredentialsNonExpired(true);
        userDetails.setEnabled(true);
        // 当前用户锁拥有的角色列表
        userDetails.setAuthorities(list);

        return userDetails;
    }

    // 前端会传两个参数过来：username用户名，password密码
    public String login(LoginDto loginDto) {
        // 1. 查看用户名是否存在，并且把用户的账号、密码、权限列表等一起查出来，存储在一个UserDetails结构中
        UserDetails userDetails = this.loadUserByUsername(loginDto.getUsername());
        // 2. 比对密码是否正确
        String password = userDetails.getPassword();
        if (password.equals(loginDto.getPassword())) {
            // 3. 生成Token
            return generateToken(userDetails);
        }

        throw new RuntimeException("密码错误");
    }

    private String generateToken(UserDetails userDetails) {
        Instant now = Instant.now();
        long expiry = 36000L;

        // 1. 获取用户信息中的 authorities集合中的authority字段，使用空格分隔，组装成一个字符串
        // 这个字符串其实就是控制器校验权限时，使用 @PreAuthorize("hasAuthority('app')") 注解校验的内容
        // @formatter:off
        String scope = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));

        // 2. 生成 JWT-token
        // 这个是 security 内置的方法，建议使用
        // issuer 为创建者
        // issuedAt 创建时间
        // subject token中的sub字段

        // expiresAt 过期时间（重要）
        // claim 代表的是存储在载荷中的键值对 -> scope后续用来校验某个接口是否有权限的关键位置
        // 这里的scope我们用的是，当前用户的角色列表，如果当前用户拥有多个角色那么 中间用空格隔开
        // ["admin", "test"] -> "admin test"
        // 通过 build 方法得到 JwtClaimsSet 对象
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plusSeconds(expiry))
                .subject(userDetails.getUsername())
                .claim("scope", scope)
                .build();

        // 3. 使用 jwtEncoder.encode  方法对上面那步的 JwtClaimsSet 对象进行加密，从而得到最终的token
        // jwtEncoder.encode 这个方法其实就是在config中声明的
        // @formatter:on
        return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }
}
